package top.jiangqiang.qyblog.core.service.impl;

import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import top.jiangqiang.qyblog.common.base.query.PageResult;
import top.jiangqiang.qyblog.common.base.response.Result;
import top.jiangqiang.qyblog.common.config.GlobalProperties;
import top.jiangqiang.qyblog.common.exception.JsonException;
import top.jiangqiang.qyblog.core.base.service.impl.BaseServiceImpl;
import top.jiangqiang.qyblog.core.constant.StorageTypeEnum;
import top.jiangqiang.qyblog.core.domain.dto.BatchUploadFileDto;
import top.jiangqiang.qyblog.core.domain.dto.FileQueryDto;
import top.jiangqiang.qyblog.core.domain.dto.UploadByUrlDto;
import top.jiangqiang.qyblog.core.domain.dto.UploadFileDto;
import top.jiangqiang.qyblog.core.domain.entity.File;
import top.jiangqiang.qyblog.core.domain.entity.Storage;
import top.jiangqiang.qyblog.core.domain.vo.BatchUploadFileVo;
import top.jiangqiang.qyblog.core.domain.vo.FileVo;
import top.jiangqiang.qyblog.core.domain.vo.UploadByUrlVo;
import top.jiangqiang.qyblog.core.mapper.FileMapper;
import top.jiangqiang.qyblog.core.service.IFileService;
import top.jiangqiang.qyblog.core.service.IStorageService;
import top.jiangqiang.qyblog.core.storage.S3Container;
import top.jiangqiang.qyblog.core.storage.StorageClient;

import java.io.IOException;
import java.io.InputStream;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author JiangQiang
 * @since 2023-05-23
 */
@Slf4j
@Service
public class FileServiceImpl extends BaseServiceImpl<FileMapper, File> implements IFileService {
    private final IStorageService storageService;
    private final S3Container s3Container;
    protected final GlobalProperties globalProperties;
    protected final java.io.File cacheDir;

    @Override
    public MPJLambdaWrapper<File> listLambdaJoinWrapper() {
        return lambdaJoinWrapper().selectAll(File.class)
                .selectAssociation(Storage.class, FileVo::getStorage)
                .leftJoin(Storage.class, Storage::getId, File::getStorageId);
    }

    @NotNull
    public InputStream getInputStream(MultipartFile multipartFile) {
        try {
            return multipartFile.getInputStream();
        } catch (IOException e) {
            throw new JsonException("文件上传失败");
        }
    }

    @Override
    public Result<FileVo> uploadFile(UploadFileDto uploadFileDto) {
        Storage storage = storageService.getStorage(uploadFileDto.getStorageId());
        StorageClient storageClient = getStorageClient(storage, uploadFileDto.getIsSync());
        File file = buildFile(uploadFileDto.getFile(), storage);
        file.setUrl(storageClient.getUrl(file.getFileKey()));
        save(file);
        InputStream inputStream = getInputStream(uploadFileDto.getFile());
        storageClient.upload(file, inputStream, this::updateById);
        return getFileDetail(file.getId());
    }

    @Override
    public Result<BatchUploadFileVo> uploadFiles(BatchUploadFileDto batchUploadFileDto) {
        Storage storage = storageService.getStorage(batchUploadFileDto.getStorageId());
        StorageClient storageClient = getStorageClient(storage, batchUploadFileDto.getIsSync());
        BatchUploadFileVo batchUploadFileVo = new BatchUploadFileVo();
        for (MultipartFile multipartFile : batchUploadFileDto.getFiles()) {
            String filename = multipartFile.getOriginalFilename();
            try {
                File file = buildFile(multipartFile, storage);
                file.setUrl(storageClient.getUrl(file.getFileKey()));
                save(file);
                InputStream inputStream = getInputStream(multipartFile);
                storageClient.upload(file, inputStream, this::updateById);
                batchUploadFileVo.putSuccessFile(filename, file.getUrl());
            } catch (Exception e) {
                log.debug("文件" + filename + "上传失败", e);
                batchUploadFileVo.putErrorFile(filename, e.getMessage());
            }
        }
        return Result.ok(batchUploadFileVo);
    }

    @Override
    public Result<UploadByUrlVo> uploadFileByUrl(UploadByUrlDto uploadByUrlDto) {
        Storage storage = storageService.getStorage(uploadByUrlDto.getStorageId());
        StorageClient storageClient = getStorageClient(storage, uploadByUrlDto.getIsSync());
        String originalURL = uploadByUrlDto.getUrl();
        File file = downloadToCacheDir(originalURL, storage);
        file.setUrl(storageClient.getUrl(file.getFileKey()));
        save(file);

        String fileKey = file.getFileKey();
        String keyId = FileNameUtil.mainName(fileKey);
        //缓存文件
        java.io.File tmpFile = FileUtil.file(cacheDir, keyId);
        storageClient.upload(file, FileUtil.getInputStream(tmpFile), this::updateById);
        UploadByUrlVo uploadByUrlVo = new UploadByUrlVo();
        uploadByUrlVo.setOriginalURL(originalURL);
        uploadByUrlVo.setUrl(file.getUrl());
        return Result.ok(uploadByUrlVo);
    }

    @Override
    public Result<FileVo> getFileDetail(Long id) {
        FileVo fileVo = lambdaJoinWrapper().selectAll(File.class)
                .selectAssociation(Storage.class, FileVo::getStorage)
                .leftJoin(Storage.class, Storage::getId, File::getStorageId)
                .eq(File::getId, id)
                .one(FileVo.class);
        if (fileVo == null) {
            return Result.error("文件不存在");
        }
        return Result.ok(fileVo);
    }

    @Override
    public Result<Boolean> deleteFile(Long id) {
        File file = baseMapper.selectById(id);
        if (file == null) {
            return Result.error("文件不存在");
        }
        if (file.getState() == 2) {
            return Result.error("文件正在上传中，暂时无法删除");
        }
        Storage storage = storageService.getStorage(file.getStorageId());
        StorageClient storageClient = getStorageClient(storage, true);
        boolean delete = storageClient.delete(file.getFileKey());
        if (delete) {
            return Result.ok(removeById(file));
        } else {
            return Result.error("删除失败");
        }
    }

    @Override
    public PageResult<FileVo> findByCondition(FileQueryDto query) {
        return lambdaJoinWrapper().like(StrUtil.isNotBlank(query.getName()), File::getName, query.getName())
                .orderByDesc(File::getCreateTime).page(query.toPage(), FileVo.class);
    }

    /**
     * 从url下载文件到指定目录
     *
     * @param url http(s)协议的url链接
     * @return
     */
    private File downloadToCacheDir(String url, Storage storage) {
        //获取一个全局唯一ID
        String keyId = genFileKey(null);
        //下载到缓存目录
        java.io.File tmpFile = FileUtil.file(cacheDir, keyId);
        HttpUtil.downloadFileFromUrl(url, tmpFile);
        //扩展名
        String extName = FileTypeUtil.getType(tmpFile);
        String fileKey;
        //如果扩展名不为空，key=id+"."+extName
        if (StrUtil.isNotBlank(extName)) {
            fileKey = keyId + "." + extName;
        } else {
            fileKey = keyId;
        }
        return new File()
                .setFileKey(fileKey)
                .setName(fileKey)
                .setSize(tmpFile.length())
                .setStorageId(storage.getId())
                .setType(FileUtil.getMimeType(tmpFile.toPath()))
                .setState(2);
    }

    /**
     * 通过MultipartFile类型构建一个File数据对象
     *
     * @param multipartFile
     * @return
     */
    private File buildFile(MultipartFile multipartFile, Storage storage) {
        String fileName = multipartFile.getOriginalFilename();
        String fileKey = genFileKey(fileName);
        String dirName = storage.getConfig().getDirName();
        if (StrUtil.isNotBlank(dirName)) {
            fileKey = dirName + "/" + fileKey;
        }
        return new File()
                .setFileKey(fileKey)
                .setName(fileName)
                .setSize(multipartFile.getSize())
                .setStorageId(storage.getId())
                .setType(multipartFile.getContentType())
                .setState(2);
    }

    public FileServiceImpl(IStorageService storageService, S3Container s3Container, GlobalProperties globalProperties) {
        this.storageService = storageService;
        this.s3Container = s3Container;
        this.globalProperties = globalProperties;
        this.cacheDir = FileUtil.file(globalProperties.getFileConfig().getCachePath());
    }

    @NotNull
    private StorageClient getStorageClient(Storage storage, Boolean isSync) {
        StorageTypeEnum type = storage.getType();
        return switch (type) {
            case LOCAL -> throw new JsonException("暂时不支持本地存储");
            case S3 -> s3Container.getStorageClient(storage, isSync);
        };
    }
}
